Purpose
The purpose of this notebook is to use the raw travel time data to experiment with different methods of aggregation and score modeling.
Import libraries
library(tidyverse)
library(ggplot2)
# For pretty knitting
library(lemon)
knit_print.data.frame <- lemon_print
knit_print.tbl <- lemon_print
knit_print.summary <- lemon_print
Import Scoring Functions
source('Score_Functions.R')
# normalize_vec(vec, x=0.01, y=0.99, log = FALSE)
# normalize_df(df, x = 0.01, y = 0.99, log = FALSE)
# sum score function : SUM [i..n] (1 / (traveltime_i * std_traveltime_i) + ... ))
# sum_score_fxn(df, weight = FALSE, log_normalize_score = TRUE, normalize_df = FALSE, x=1, y=10)
# function to plot score distributions by type
plot_densities <- function(score_frame1, score_frame2, titl1, titl2) {
x <- score_frame1 %>%
ggplot(aes(x = score, color = type)) +
geom_density() +
egg::theme_article() +
theme(aspect.ratio = 0.3) +
ggtitle(titl1)
y <- score_frame2 %>%
ggplot(aes(x = score, color = type)) +
geom_density() +
egg::theme_article() +
theme(aspect.ratio = 0.3)+
ggtitle(titl2)
gridExtra::grid.arrange(x, y)
}
Import data
## Import raw Travel Time Matrix (ttm)
ttm <- read.csv('../../data/clean/ttm.csv')
n_origins <- 15197 # known origins
n_amenities <- 346 # known destinations from considered amenities
paste('Origins considered:', round(length(unique(ttm$fromId))/n_origins*100, 2), '%')
[1] "Origins considered: 94.44 %"
paste('Destinations considered:', round(length(unique(ttm$toId))/n_amenities*100, 2), '%')
[1] "Destinations considered: 124.57 %"
paste('Rows = ', nrow(ttm))
[1] "Rows = 5162695"
# convert Ids from double to factor
ttm$fromId <- as.factor(ttm$fromId)
ttm$toId <- as.factor(ttm$toId)
summary(ttm[,3:4])
avg_unique_time sd_unique_time
Min. : 0.00 Min. : 0.1601
1st Qu.: 52.54 1st Qu.: 1.9428
Median : 72.18 Median : 2.8868
Mean : 72.79 Mean : 3.4044
3rd Qu.: 94.21 3rd Qu.: 4.3813
Max. :119.00 Max. :35.3553
Data Wrangling
Wrangling Notes: - Remove skews and extreme values - Due to the diversity in amenity types (which all serve a unique cultural purpose), we’ll consider them independently for accessibility score computations. - Amenities which were interested in studying have already been filtered out in the ttm computation. They are the following: - Museums - Libraries - Galleries - Theatres
Import and join amenity types
target_amenities <- c('gallery', 'museum', 'library or archives', 'theatre/performance and concert hall')
amenities <- read.csv('../../data/clean/vancouver_facilities_2.csv') %>% filter(type %in% target_amenities)
# preview original
sample_n(amenities, 3)
# clean
amenities <- amenities[,c(1,4)] # only need id and type columns
amenities$id <- as.factor(amenities$id) # convert to factor
amenities$type <- as.factor(amenities$type) # convert to factor
# preview clean
sample_n(amenities, 3)
# view summary
amenities %>% group_by(type) %>% summarise(count = n()) %>% arrange(desc(count))
ttm <- ttm %>% left_join(amenities, by = c('toId' = 'id'))
names(ttm)[names(ttm) == 'avg_unique_time'] <- "avg_time"
names(ttm)[names(ttm) == 'sd_unique_time'] <- "sd_time"
summary(ttm[,3:4])
avg_time sd_time
Min. : 0.00 Min. : 0.1601
1st Qu.: 52.54 1st Qu.: 1.9428
Median : 72.18 Median : 2.8868
Mean : 72.79 Mean : 3.4044
3rd Qu.: 94.21 3rd Qu.: 4.3813
Max. :119.00 Max. :35.3553
sample_n(ttm, 5)
par(mfrow = c(1,2))
plot(density(ttm[,3]), main = 'Travel Time (Density)')
plot(density(ttm[,4]), main = 'Std Dev of Travel Time (Density)')

Replace travel times less than 5 minutes to 5 minutes
This is done to prevent infinity values in the scoring. Normalization will be done to prevent zero values but it still creates a largely skewed score if we include travel times that approach zero. 5 minutes is also a realistic time window for any travel time that may take 0 - 5 minutes.
par(mfrow = c(1, 2))
hist((ttm$avg_time), xlab = 'Original Travel Time', main = '',
xlim = c(0, 25), ylim = c(0, 120000))
# set travel times <5 minutes to 5 minutes
min_5min <- pmax(ttm$avg_time, 5)
hist(min_5min, xlab = 'Original Travel Time', main = '',
xlim = c(0, 25), ylim = c(0, 120000))

ttm$avg_time <- min_5min
Correct skew in standard deviation
This will be important to prevent skew amplification in the score computation.
# correct the skew in addition to edges close to zero
temp <- log(ttm$sd_time + 1) # +1 just prevents zero values
plot(density(temp), main = 'Log+1 Standard Deviation Density', xlim = c(0,4))

# set sd_unique_time to be the Log+1 corrected values
ttm$sd_time <- temp
Add Amenity Weights
# Import weight
dest_wts <- read.csv('../../data/amenity_score/poi_index.csv')
# clean
dest_wts <- dest_wts[, c(6,7)] # keep weight, id
names(dest_wts) <- c('weight', 'id')
dest_wts$id <- as.factor(dest_wts$id)
head(dest_wts)
# see weight distribution
plot(density(dest_wts$weight), main = 'Amenity Popularity Distribution')

# join column
ttm_wts <- left_join(ttm, dest_wts, by = c('toId'='id'))
# If any weights are undefined replace with 1
ttm_wts$weight[is.na(ttm_wts$weight)] <- 1
head(ttm_wts)
NA
NA
Sum Scoring Method
# scores with [1 - 100] df normalization
na.omit(ttm_wts)->ttm_wts
ttm_scores <- sum_score_fxn(ttm_wts,
weight = FALSE,
log_normalize_score = TRUE,
normalize_df = TRUE, x = 1, y = 100)
ttm_wtd_scores <- sum_score_fxn(ttm_wts,
weight = TRUE,
log_normalize_score = TRUE,
normalize_df = TRUE, x = 1, y = 100)
Sum Scoring Method 2 with mean plus sd
ttm_scores_2 <- sum_score_fxn_2(ttm_wts,
weight = FALSE,
log_normalize_score = TRUE,
normalize_df = TRUE, x = 1, y = 100)
ttm_wtd_scores_2 <- sum_score_fxn_2(ttm_wts,
weight = TRUE,
log_normalize_score = TRUE,
normalize_df = TRUE, x = 1, y = 100)

# scores with [0.01 - 0.99] df normalization
ttm_scores2 <- sum_score_fxn(ttm_wts,
weight = FALSE,
log_normalize_score = TRUE,
normalize_df = TRUE, x = 0.01, y = 0.99)
ttm_wtd_scores2 <- sum_score_fxn(ttm_wts,
weight = TRUE,
log_normalize_score = TRUE,
normalize_df = TRUE, x = 0.01, y = 0.99)
plot_densities(ttm_scores2, ttm_wtd_scores2, 'Unweighted Scores', 'Weighted Scores')
Sum Scoring for the Nearest 1, 2, or 3 Amenities
Note that for nearest 1, the sum is the value itself.
# Keep only the nearest 1, 2, or 3 travel times for each dissemination block
nearest_1_ttm <- ttm_wts %>%
group_by(fromId, type) %>%
summarise(avg_time = min(avg_time),
sd_time = sd_time[which.min(avg_time)],
weight = weight[which.min(avg_time)])
nearest_2_ttm <- ttm_wts %>%
group_by(fromId, type) %>%
summarise(avg_time = na.omit(sort(avg_time)[1:2]),
sd_time = sd_time[which(na.omit(avg_time == sort(avg_time)[1:2]))],
weight = weight[which(na.omit(avg_time == sort(avg_time)[1:2]))])
nearest_3_ttm <- ttm_wts %>%
group_by(fromId, type) %>%
summarise(avg_time = na.omit(sort(avg_time)[1:3]),
sd_time = sd_time[which(na.omit(avg_time == sort(avg_time)[1:3]))],
weight = weight[which(na.omit(avg_time == sort(avg_time)[1:3]))])
# scores by nearest amenities
n1_ttm_score <- sum_score_fxn(nearest_1_ttm, weight = FALSE, log_normalize_score = TRUE, normalize_df = TRUE, x = 1, y = 100)
n1_wt_ttm_score <- sum_score_fxn(nearest_1_ttm, weight = TRUE, log_normalize_score = TRUE, normalize_df = TRUE, x = 1, y = 100)
n2_ttm_score <- sum_score_fxn(nearest_2_ttm, weight = FALSE, log_normalize_score = TRUE, normalize_df = TRUE, x = 1, y = 100)
n2_wt_ttm_score <- sum_score_fxn(nearest_2_ttm, weight = TRUE, log_normalize_score = TRUE, normalize_df = TRUE, x = 1, y = 100)
n3_ttm_score <- sum_score_fxn(nearest_3_ttm, weight = FALSE, log_normalize_score = TRUE, normalize_df = TRUE, x = 1, y = 100)
n3_wt_ttm_score <- sum_score_fxn(nearest_3_ttm, weight = TRUE, log_normalize_score = TRUE, normalize_df = TRUE, x = 1, y = 100)
plot_densities(n1_ttm_score, n1_wt_ttm_score, 'Unweighted Scores with 1/(Mean + 2*Sd)', 'Weighted Scores with 1/(Mean+2*Sd)')

plot_densities(n2_ttm_score, n2_wt_ttm_score, 'Unweighted Scores with 1/(Mean + 2*Sd)', 'Weighted Scores with 1/(Mean+2*Sd)')

plot_densities(n3_ttm_score, n3_wt_ttm_score, 'Unweighted Scores with 1/(Mean + 2*Sd)', 'Weighted Scores with 1/(Mean+2*Sd)')

using score function of 1/(mean+2sd)
# scores by nearest amenities
n1_ttm_score_2 <- sum_score_fxn_2(nearest_1_ttm, weight = FALSE, log_normalize_score = TRUE, normalize_df = TRUE, x = 1, y = 100)
`summarise()` has grouped output by 'fromId'. You can override using the `.groups` argument.
n1_wt_ttm_score_2 <- sum_score_fxn_2(nearest_1_ttm, weight = TRUE, log_normalize_score = TRUE, normalize_df = TRUE, x = 1, y = 100)
`summarise()` has grouped output by 'fromId'. You can override using the `.groups` argument.
n2_ttm_score_2 <- sum_score_fxn_2(nearest_2_ttm, weight = FALSE, log_normalize_score = TRUE, normalize_df = TRUE, x = 1, y = 100)
`summarise()` has grouped output by 'fromId'. You can override using the `.groups` argument.
n2_wt_ttm_score_2 <- sum_score_fxn_2(nearest_2_ttm, weight = TRUE, log_normalize_score = TRUE, normalize_df = TRUE, x = 1, y = 100)
`summarise()` has grouped output by 'fromId'. You can override using the `.groups` argument.
n3_ttm_score_2 <- sum_score_fxn_2(nearest_3_ttm, weight = FALSE, log_normalize_score = TRUE, normalize_df = TRUE, x = 1, y = 100)
`summarise()` has grouped output by 'fromId'. You can override using the `.groups` argument.
n3_wt_ttm_score_2 <- sum_score_fxn(nearest_3_ttm, weight = TRUE, log_normalize_score = TRUE, normalize_df = TRUE, x = 1, y = 100)
`summarise()` has grouped output by 'fromId'. You can override using the `.groups` argument.
plot_densities(n1_ttm_score_2, n1_wt_ttm_score_2, 'Unweighted Scores with 1/(Mean + 2*Sd)', 'Weighted Scores with 1/(Mean+2*Sd)')

plot_densities(n2_ttm_score_2, n2_wt_ttm_score_2, 'Unweighted Scores with 1/(Mean + 2*Sd)', 'Weighted Scores with 1/(Mean+2*Sd)')

plot_densities(n3_ttm_score_2, n3_wt_ttm_score_2, 'Unweighted Scores with 1/(Mean + 2*Sd)', 'Weighted Scores with 1/(Mean+2*Sd)')

Exporting all Score Sets
## Add weight column for each score frame
ttm_scores$weight <- as.factor('no')
ttm_wtd_scores$weight <- as.factor('yes')
n1_ttm_score$weight <- as.factor('no')
n1_wt_ttm_score$weight <- as.factor('yes')
n2_ttm_score$weight <- as.factor('no')
n2_wt_ttm_score$weight <- as.factor('yes')
n3_ttm_score$weight <- as.factor('no')
n3_wt_ttm_score$weight <- as.factor('yes')
## Add nearest_n column for each score frame
ttm_scores$nearest_n <- as.factor('all')
ttm_wtd_scores$nearest_n <- as.factor('all')
n1_ttm_score$nearest_n <- as.factor('1')
n1_wt_ttm_score$nearest_n <- as.factor('1')
n2_ttm_score$nearest_n <- as.factor('2')
n2_wt_ttm_score$nearest_n <- as.factor('2')
n3_ttm_score$nearest_n <- as.factor('3')
n3_wt_ttm_score$nearest_n <- as.factor('3')
## Combine into a long dataframe
all_scores <- list(ttm_scores, ttm_wtd_scores,
n1_ttm_score, n1_wt_ttm_score,
n2_ttm_score, n2_wt_ttm_score,
n3_ttm_score, n3_wt_ttm_score)
long_scores <- data.table::rbindlist(all_scores) %>% arrange(fromId)
## Re-Order columns
long_scores <- long_scores[, c(1, 2, 4, 5, 3)]
## Export
write.csv(long_scores, '../../data/score_sets/long_scores.csv', row.names = FALSE)
LS0tCnRpdGxlOiAiU2NvcmUgQ29tcHV0YXRpb24gTm90ZWJvb2siCm91dHB1dDoKICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQKICBodG1sX25vdGVib29rOiBkZWZhdWx0CiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAotLS0KCiMgUHVycG9zZQoKVGhlIHB1cnBvc2Ugb2YgdGhpcyBub3RlYm9vayBpcyB0byB1c2UgdGhlIHJhdyB0cmF2ZWwgdGltZSBkYXRhIHRvIGV4cGVyaW1lbnQgd2l0aCBkaWZmZXJlbnQgbWV0aG9kcyBvZiBhZ2dyZWdhdGlvbiBhbmQgc2NvcmUgbW9kZWxpbmcuCgojIyBJbXBvcnQgbGlicmFyaWVzCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGdncGxvdDIpCgojIEZvciBwcmV0dHkga25pdHRpbmcKbGlicmFyeShsZW1vbikKa25pdF9wcmludC5kYXRhLmZyYW1lIDwtIGxlbW9uX3ByaW50CmtuaXRfcHJpbnQudGJsIDwtIGxlbW9uX3ByaW50CmtuaXRfcHJpbnQuc3VtbWFyeSA8LSBsZW1vbl9wcmludAoKCmBgYAoKIyMgSW1wb3J0IFNjb3JpbmcgRnVuY3Rpb25zCmBgYHtyfQpzb3VyY2UoJ1Njb3JlX0Z1bmN0aW9ucy5SJykKCiMgbm9ybWFsaXplX3ZlYyh2ZWMsIHg9MC4wMSwgeT0wLjk5LCBsb2cgPSBGQUxTRSkKIyBub3JtYWxpemVfZGYoZGYsIHggPSAwLjAxLCB5ID0gMC45OSwgbG9nID0gRkFMU0UpCgojIHN1bSBzY29yZSBmdW5jdGlvbiA6IFNVTSBbaS4ubl0gKDEgLyAodHJhdmVsdGltZV9pICogc3RkX3RyYXZlbHRpbWVfaSkgKyAuLi4gKSkKIyBzdW1fc2NvcmVfZnhuKGRmLCB3ZWlnaHQgPSBGQUxTRSwgbG9nX25vcm1hbGl6ZV9zY29yZSA9IFRSVUUsIG5vcm1hbGl6ZV9kZiA9IEZBTFNFLCB4PTEsIHk9MTApCgpgYGAKCmBgYHtyfQojIGZ1bmN0aW9uIHRvIHBsb3Qgc2NvcmUgZGlzdHJpYnV0aW9ucyBieSB0eXBlCnBsb3RfZGVuc2l0aWVzIDwtIGZ1bmN0aW9uKHNjb3JlX2ZyYW1lMSwgc2NvcmVfZnJhbWUyLCB0aXRsMSwgdGl0bDIpIHsKICB4IDwtIHNjb3JlX2ZyYW1lMSAlPiUKICAgICAgICBnZ3Bsb3QoYWVzKHggPSBzY29yZSwgY29sb3IgPSB0eXBlKSkgKwogICAgICAgIGdlb21fZGVuc2l0eSgpICsKICAgICAgICBlZ2c6OnRoZW1lX2FydGljbGUoKSArCiAgICAgICAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMC4zKSArCiAgICAgICAgZ2d0aXRsZSh0aXRsMSkKICB5IDwtIHNjb3JlX2ZyYW1lMiAlPiUKICAgICAgICBnZ3Bsb3QoYWVzKHggPSBzY29yZSwgY29sb3IgPSB0eXBlKSkgKwogICAgICAgIGdlb21fZGVuc2l0eSgpICsKICAgICAgICBlZ2c6OnRoZW1lX2FydGljbGUoKSArCiAgICAgICAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMC4zKSsKICAgICAgICBnZ3RpdGxlKHRpdGwyKQogIGdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKHgsIHkpCn0KCmBgYAoKCiMjIEltcG9ydCBkYXRhIApgYGB7ciBrYWJsZS5vcHRzPWxpc3QoY2FwdGlvbj0nU3VtbWFyeSBUYWJsZScpfQoKIyMgSW1wb3J0IHJhdyBUcmF2ZWwgVGltZSBNYXRyaXggKHR0bSkKdHRtIDwtIHJlYWQuY3N2KCcuLi8uLi9kYXRhL2NsZWFuL3R0bS5jc3YnKQoKbl9vcmlnaW5zIDwtIDE1MTk3ICMga25vd24gb3JpZ2lucwpuX2FtZW5pdGllcyA8LSAzNDYgIyBrbm93biBkZXN0aW5hdGlvbnMgZnJvbSBjb25zaWRlcmVkIGFtZW5pdGllcwoKcGFzdGUoJ09yaWdpbnMgY29uc2lkZXJlZDonLCByb3VuZChsZW5ndGgodW5pcXVlKHR0bSRmcm9tSWQpKS9uX29yaWdpbnMqMTAwLCAyKSwgJyUnKQpwYXN0ZSgnRGVzdGluYXRpb25zIGNvbnNpZGVyZWQ6Jywgcm91bmQobGVuZ3RoKHVuaXF1ZSh0dG0kdG9JZCkpL25fYW1lbml0aWVzKjEwMCwgMiksICclJykKcGFzdGUoJ1Jvd3MgPSAnLCBucm93KHR0bSkpCgojIGNvbnZlcnQgSWRzIGZyb20gZG91YmxlIHRvIGZhY3Rvcgp0dG0kZnJvbUlkIDwtIGFzLmZhY3Rvcih0dG0kZnJvbUlkKQp0dG0kdG9JZCA8LSBhcy5mYWN0b3IodHRtJHRvSWQpCgpzdW1tYXJ5KHR0bVssMzo0XSkKYGBgCgojIyBEYXRhIFdyYW5nbGluZyAKCioqV3JhbmdsaW5nIE5vdGVzOioqCi0gUmVtb3ZlIHNrZXdzIGFuZCBleHRyZW1lIHZhbHVlcwotIER1ZSB0byB0aGUgZGl2ZXJzaXR5IGluIGFtZW5pdHkgdHlwZXMgKHdoaWNoIGFsbCBzZXJ2ZSBhIHVuaXF1ZSBjdWx0dXJhbCBwdXJwb3NlKSwgd2UnbGwgY29uc2lkZXIgdGhlbSBpbmRlcGVuZGVudGx5IGZvciBhY2Nlc3NpYmlsaXR5IHNjb3JlIGNvbXB1dGF0aW9ucy4KLSBBbWVuaXRpZXMgd2hpY2ggd2VyZSBpbnRlcmVzdGVkIGluIHN0dWR5aW5nIGhhdmUgYWxyZWFkeSBiZWVuIGZpbHRlcmVkIG91dCBpbiB0aGUgdHRtIGNvbXB1dGF0aW9uLiBUaGV5IGFyZSB0aGUgZm9sbG93aW5nOgogIC0gTXVzZXVtcwogIC0gTGlicmFyaWVzCiAgLSBHYWxsZXJpZXMKICAtIFRoZWF0cmVzCgoqKkltcG9ydCBhbmQgam9pbiBhbWVuaXR5IHR5cGVzKioKCmBgYHtyIGthYmxlLm9wdHM9bGlzdChjYXB0aW9uPSdTdW1tYXJ5IFRhYmxlJyl9Cgp0YXJnZXRfYW1lbml0aWVzIDwtIGMoJ2dhbGxlcnknLCAnbXVzZXVtJywgJ2xpYnJhcnkgb3IgYXJjaGl2ZXMnLCAndGhlYXRyZS9wZXJmb3JtYW5jZSBhbmQgY29uY2VydCBoYWxsJykKYW1lbml0aWVzIDwtIHJlYWQuY3N2KCcuLi8uLi9kYXRhL2NsZWFuL3ZhbmNvdXZlcl9mYWNpbGl0aWVzXzIuY3N2JykgJT4lIGZpbHRlcih0eXBlICVpbiUgdGFyZ2V0X2FtZW5pdGllcykKCiMgcHJldmlldyBvcmlnaW5hbApzYW1wbGVfbihhbWVuaXRpZXMsIDMpCgojIGNsZWFuCmFtZW5pdGllcyA8LSBhbWVuaXRpZXNbLGMoMSw0KV0gIyBvbmx5IG5lZWQgaWQgYW5kIHR5cGUgY29sdW1ucwphbWVuaXRpZXMkaWQgPC0gYXMuZmFjdG9yKGFtZW5pdGllcyRpZCkgICAgICMgY29udmVydCB0byBmYWN0b3IKYW1lbml0aWVzJHR5cGUgPC0gYXMuZmFjdG9yKGFtZW5pdGllcyR0eXBlKSAjIGNvbnZlcnQgdG8gZmFjdG9yCgojIHByZXZpZXcgY2xlYW4Kc2FtcGxlX24oYW1lbml0aWVzLCAzKQoKIyB2aWV3IHN1bW1hcnkKYW1lbml0aWVzICU+JSBncm91cF9ieSh0eXBlKSAlPiUgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUgYXJyYW5nZShkZXNjKGNvdW50KSkKCnR0bSA8LSB0dG0gJT4lIGxlZnRfam9pbihhbWVuaXRpZXMsIGJ5ID0gYygndG9JZCcgPSAnaWQnKSkKCm5hbWVzKHR0bSlbbmFtZXModHRtKSA9PSAnYXZnX3VuaXF1ZV90aW1lJ10gPC0gImF2Z190aW1lIgpuYW1lcyh0dG0pW25hbWVzKHR0bSkgPT0gJ3NkX3VuaXF1ZV90aW1lJ10gPC0gInNkX3RpbWUiCgpzdW1tYXJ5KHR0bVssMzo0XSkKc2FtcGxlX24odHRtLCA1KQoKcGFyKG1mcm93ID0gYygxLDIpKQpwbG90KGRlbnNpdHkodHRtWywzXSksIG1haW4gPSAnVHJhdmVsIFRpbWUgKERlbnNpdHkpJykKcGxvdChkZW5zaXR5KHR0bVssNF0pLCBtYWluID0gJ1N0ZCBEZXYgb2YgVHJhdmVsIFRpbWUgKERlbnNpdHkpJykKYGBgCgoKKipSZXBsYWNlIHRyYXZlbCB0aW1lcyBsZXNzIHRoYW4gNSBtaW51dGVzIHRvIDUgbWludXRlcyoqCgpUaGlzIGlzIGRvbmUgdG8gcHJldmVudCBpbmZpbml0eSB2YWx1ZXMgaW4gdGhlIHNjb3JpbmcuIE5vcm1hbGl6YXRpb24gd2lsbCBiZSBkb25lIHRvIHByZXZlbnQgemVybyB2YWx1ZXMgYnV0IGl0IHN0aWxsIGNyZWF0ZXMgYSBsYXJnZWx5IHNrZXdlZCBzY29yZSBpZiB3ZSBpbmNsdWRlIHRyYXZlbCB0aW1lcyB0aGF0IGFwcHJvYWNoIHplcm8uIDUgbWludXRlcyBpcyBhbHNvIGEgcmVhbGlzdGljIHRpbWUgd2luZG93IGZvciBhbnkgdHJhdmVsIHRpbWUgdGhhdCBtYXkgdGFrZSAwIC0gNSBtaW51dGVzLgoKYGBge3J9CnBhcihtZnJvdyA9IGMoMSwgMikpCgpoaXN0KCh0dG0kYXZnX3RpbWUpLCB4bGFiID0gJ09yaWdpbmFsIFRyYXZlbCBUaW1lJywgbWFpbiA9ICcnLAogICAgIHhsaW0gPSBjKDAsIDI1KSwgeWxpbSA9IGMoMCwgMTIwMDAwKSkKCiMgc2V0IHRyYXZlbCB0aW1lcyA8NSBtaW51dGVzIHRvIDUgbWludXRlcwptaW5fNW1pbiA8LSBwbWF4KHR0bSRhdmdfdGltZSwgNSkKaGlzdChtaW5fNW1pbiwgeGxhYiA9ICdPcmlnaW5hbCBUcmF2ZWwgVGltZScsIG1haW4gPSAnJywKICAgICB4bGltID0gYygwLCAyNSksIHlsaW0gPSBjKDAsIDEyMDAwMCkpCgp0dG0kYXZnX3RpbWUgPC0gbWluXzVtaW4KYGBgCgoqKkNvcnJlY3Qgc2tldyBpbiBzdGFuZGFyZCBkZXZpYXRpb24qKgoKVGhpcyB3aWxsIGJlIGltcG9ydGFudCB0byBwcmV2ZW50IHNrZXcgYW1wbGlmaWNhdGlvbiBpbiB0aGUgc2NvcmUgY29tcHV0YXRpb24uCgpgYGB7cn0KIyBjb3JyZWN0IHRoZSBza2V3IGluIGFkZGl0aW9uIHRvIGVkZ2VzIGNsb3NlIHRvIHplcm8KCnRlbXAgPC0gbG9nKHR0bSRzZF90aW1lICsgMSkgIyArMSBqdXN0IHByZXZlbnRzIHplcm8gdmFsdWVzCnBsb3QoZGVuc2l0eSh0ZW1wKSwgbWFpbiA9ICdMb2crMSBTdGFuZGFyZCBEZXZpYXRpb24gRGVuc2l0eScsIHhsaW0gPSBjKDAsNCkpCgojIHNldCBzZF91bmlxdWVfdGltZSB0byBiZSB0aGUgTG9nKzEgY29ycmVjdGVkIHZhbHVlcwp0dG0kc2RfdGltZSA8LSB0ZW1wCgpgYGAKCiMjIEFkZCBBbWVuaXR5IFdlaWdodHMKCmBgYHtyIGthYmxlLm9wdHM9bGlzdChjYXB0aW9uPSdTdW1tYXJ5IFRhYmxlJyl9CgojIEltcG9ydCB3ZWlnaHQKZGVzdF93dHMgPC0gcmVhZC5jc3YoJy4uLy4uL2RhdGEvYW1lbml0eV9zY29yZS9wb2lfaW5kZXguY3N2JykKCiMgY2xlYW4KZGVzdF93dHMgPC0gZGVzdF93dHNbLCBjKDYsNyldICMga2VlcCB3ZWlnaHQsIGlkCm5hbWVzKGRlc3Rfd3RzKSA8LSBjKCd3ZWlnaHQnLCAnaWQnKQpkZXN0X3d0cyRpZCA8LSAgYXMuZmFjdG9yKGRlc3Rfd3RzJGlkKQpoZWFkKGRlc3Rfd3RzKQoKIyBzZWUgd2VpZ2h0IGRpc3RyaWJ1dGlvbgpwbG90KGRlbnNpdHkoZGVzdF93dHMkd2VpZ2h0KSwgbWFpbiA9ICdBbWVuaXR5IFBvcHVsYXJpdHkgRGlzdHJpYnV0aW9uJykKCiMgam9pbiBjb2x1bW4KdHRtX3d0cyA8LSBsZWZ0X2pvaW4odHRtLCBkZXN0X3d0cywgYnkgPSBjKCd0b0lkJz0naWQnKSkKCiMgSWYgYW55IHdlaWdodHMgYXJlIHVuZGVmaW5lZCByZXBsYWNlIHdpdGggMQp0dG1fd3RzJHdlaWdodFtpcy5uYSh0dG1fd3RzJHdlaWdodCldIDwtIDEKCmhlYWQodHRtX3d0cykKCgpgYGAKCiMjIFN1bSBTY29yaW5nIE1ldGhvZAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCiMgc2NvcmVzIHdpdGggWzEgLSAxMDBdIGRmIG5vcm1hbGl6YXRpb24gCgoKbmEub21pdCh0dG1fd3RzKS0+dHRtX3d0cwoKCnR0bV9zY29yZXMgPC0gc3VtX3Njb3JlX2Z4bih0dG1fd3RzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgd2VpZ2h0ID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsb2dfbm9ybWFsaXplX3Njb3JlID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5vcm1hbGl6ZV9kZiA9IFRSVUUsIHggPSAxLCB5ID0gMTAwKQoKdHRtX3d0ZF9zY29yZXMgPC0gc3VtX3Njb3JlX2Z4bih0dG1fd3RzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgd2VpZ2h0ID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxvZ19ub3JtYWxpemVfc2NvcmUgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbm9ybWFsaXplX2RmID0gVFJVRSwgeCA9IDEsIHkgPSAxMDApCmBgYAojIyBTdW0gU2NvcmluZyBNZXRob2QgMiB3aXRoIG1lYW4gcGx1cyBzZAoKYGBge1J9CnR0bV9zY29yZXNfMiA8LSBzdW1fc2NvcmVfZnhuXzIodHRtX3d0cywKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdlaWdodCA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbG9nX25vcm1hbGl6ZV9zY29yZSA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBub3JtYWxpemVfZGYgPSBUUlVFLCB4ID0gMSwgeSA9IDEwMCkKCnR0bV93dGRfc2NvcmVzXzIgPC0gc3VtX3Njb3JlX2Z4bl8yKHR0bV93dHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB3ZWlnaHQgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbG9nX25vcm1hbGl6ZV9zY29yZSA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBub3JtYWxpemVfZGYgPSBUUlVFLCB4ID0gMSwgeSA9IDEwMCkKCmBgYAoKYGBge3J9CnBhcihtZnJvdz1jKDEsNCkpCnBsb3RfZGVuc2l0aWVzKHR0bV9zY29yZXMsIHR0bV93dGRfc2NvcmVzLCAnVW53ZWlnaHRlZCBTY29yZXMnLCAnV2VpZ2h0ZWQgU2NvcmVzJykKcGxvdF9kZW5zaXRpZXModHRtX3Njb3Jlc18yLCB0dG1fd3RkX3Njb3Jlc18yLCAnVW53ZWlnaHRlZCBTY29yZXMgd2l0aCAxLyhNZWFuICsgMipTZCknLCAnV2VpZ2h0ZWQgU2NvcmVzIHdpdGggMS8oTWVhbisyKlNkKScpCgpgYGAKCgpgYGB7cn0KCiMgc2NvcmVzIHdpdGggWzAuMDEgLSAwLjk5XSBkZiBub3JtYWxpemF0aW9uIAoKdHRtX3Njb3JlczIgPC0gc3VtX3Njb3JlX2Z4bih0dG1fd3RzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgd2VpZ2h0ID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsb2dfbm9ybWFsaXplX3Njb3JlID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5vcm1hbGl6ZV9kZiA9IFRSVUUsIHggPSAwLjAxLCB5ID0gMC45OSkKCnR0bV93dGRfc2NvcmVzMiA8LSBzdW1fc2NvcmVfZnhuKHR0bV93dHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB3ZWlnaHQgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbG9nX25vcm1hbGl6ZV9zY29yZSA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBub3JtYWxpemVfZGYgPSBUUlVFLCB4ID0gMC4wMSwgeSA9IDAuOTkpCgpwbG90X2RlbnNpdGllcyh0dG1fc2NvcmVzMiwgdHRtX3d0ZF9zY29yZXMyLCAnVW53ZWlnaHRlZCBTY29yZXMnLCAnV2VpZ2h0ZWQgU2NvcmVzJykKCmBgYAoKCgojIyBTdW0gU2NvcmluZyBmb3IgdGhlIE5lYXJlc3QgMSwgMiwgb3IgMyBBbWVuaXRpZXMKCipOb3RlIHRoYXQgZm9yIG5lYXJlc3QgMSwgdGhlIHN1bSBpcyB0aGUgdmFsdWUgaXRzZWxmLioKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CgojIEtlZXAgb25seSB0aGUgbmVhcmVzdCAxLCAyLCBvciAzIHRyYXZlbCB0aW1lcyBmb3IgZWFjaCBkaXNzZW1pbmF0aW9uIGJsb2NrCgpuZWFyZXN0XzFfdHRtIDwtIHR0bV93dHMgJT4lCiAgICAgICAgICAgICAgICAgIGdyb3VwX2J5KGZyb21JZCwgdHlwZSkgJT4lCiAgICAgICAgICAgICAgICAgIHN1bW1hcmlzZShhdmdfdGltZSA9IG1pbihhdmdfdGltZSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2RfdGltZSA9IHNkX3RpbWVbd2hpY2gubWluKGF2Z190aW1lKV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB3ZWlnaHQgPSB3ZWlnaHRbd2hpY2gubWluKGF2Z190aW1lKV0pCgpuZWFyZXN0XzJfdHRtIDwtIHR0bV93dHMgJT4lCiAgICAgICAgICAgICAgICAgIGdyb3VwX2J5KGZyb21JZCwgdHlwZSkgJT4lCiAgICAgICAgICAgICAgICAgIHN1bW1hcmlzZShhdmdfdGltZSA9IG5hLm9taXQoc29ydChhdmdfdGltZSlbMToyXSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2RfdGltZSA9IHNkX3RpbWVbd2hpY2gobmEub21pdChhdmdfdGltZSA9PSBzb3J0KGF2Z190aW1lKVsxOjJdKSldLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdlaWdodCA9IHdlaWdodFt3aGljaChuYS5vbWl0KGF2Z190aW1lID09IHNvcnQoYXZnX3RpbWUpWzE6Ml0pKV0pCgpuZWFyZXN0XzNfdHRtIDwtIHR0bV93dHMgJT4lCiAgICAgICAgICAgICAgICAgIGdyb3VwX2J5KGZyb21JZCwgdHlwZSkgJT4lCiAgICAgICAgICAgICAgICAgIHN1bW1hcmlzZShhdmdfdGltZSA9IG5hLm9taXQoc29ydChhdmdfdGltZSlbMTozXSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2RfdGltZSA9IHNkX3RpbWVbd2hpY2gobmEub21pdChhdmdfdGltZSA9PSBzb3J0KGF2Z190aW1lKVsxOjNdKSldLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdlaWdodCA9IHdlaWdodFt3aGljaChuYS5vbWl0KGF2Z190aW1lID09IHNvcnQoYXZnX3RpbWUpWzE6M10pKV0pCgoKIyBzY29yZXMgYnkgbmVhcmVzdCBhbWVuaXRpZXMKCm4xX3R0bV9zY29yZSA8LSBzdW1fc2NvcmVfZnhuKG5lYXJlc3RfMV90dG0sIHdlaWdodCA9IEZBTFNFLCBsb2dfbm9ybWFsaXplX3Njb3JlID0gVFJVRSwgbm9ybWFsaXplX2RmID0gVFJVRSwgeCA9IDEsIHkgPSAxMDApCm4xX3d0X3R0bV9zY29yZSA8LSBzdW1fc2NvcmVfZnhuKG5lYXJlc3RfMV90dG0sIHdlaWdodCA9IFRSVUUsIGxvZ19ub3JtYWxpemVfc2NvcmUgPSBUUlVFLCBub3JtYWxpemVfZGYgPSBUUlVFLCB4ID0gMSwgeSA9IDEwMCkKCm4yX3R0bV9zY29yZSA8LSBzdW1fc2NvcmVfZnhuKG5lYXJlc3RfMl90dG0sIHdlaWdodCA9IEZBTFNFLCBsb2dfbm9ybWFsaXplX3Njb3JlID0gVFJVRSwgbm9ybWFsaXplX2RmID0gVFJVRSwgeCA9IDEsIHkgPSAxMDApCm4yX3d0X3R0bV9zY29yZSA8LSBzdW1fc2NvcmVfZnhuKG5lYXJlc3RfMl90dG0sIHdlaWdodCA9IFRSVUUsIGxvZ19ub3JtYWxpemVfc2NvcmUgPSBUUlVFLCBub3JtYWxpemVfZGYgPSBUUlVFLCB4ID0gMSwgeSA9IDEwMCkKCm4zX3R0bV9zY29yZSA8LSBzdW1fc2NvcmVfZnhuKG5lYXJlc3RfM190dG0sIHdlaWdodCA9IEZBTFNFLCBsb2dfbm9ybWFsaXplX3Njb3JlID0gVFJVRSwgbm9ybWFsaXplX2RmID0gVFJVRSwgeCA9IDEsIHkgPSAxMDApCm4zX3d0X3R0bV9zY29yZSA8LSBzdW1fc2NvcmVfZnhuKG5lYXJlc3RfM190dG0sIHdlaWdodCA9IFRSVUUsIGxvZ19ub3JtYWxpemVfc2NvcmUgPSBUUlVFLCBub3JtYWxpemVfZGYgPSBUUlVFLCB4ID0gMSwgeSA9IDEwMCkKCmBgYAoKYGBge3J9CnBsb3RfZGVuc2l0aWVzKG4xX3R0bV9zY29yZSwgbjFfd3RfdHRtX3Njb3JlLCAnVW53ZWlnaHRlZCBTY29yZXMnLCAnV2VpZ2h0ZWQgU2NvcmVzJykKcGxvdF9kZW5zaXRpZXMobjJfdHRtX3Njb3JlLCBuMl93dF90dG1fc2NvcmUsICdVbndlaWdodGVkIFNjb3JlcycsICdXZWlnaHRlZCBTY29yZXMnKQpwbG90X2RlbnNpdGllcyhuM190dG1fc2NvcmUsIG4zX3d0X3R0bV9zY29yZSwgJ1Vud2VpZ2h0ZWQgU2NvcmVzJywgJ1dlaWdodGVkIFNjb3JlcycpCgpgYGAKCiMjIyB1c2luZyBzY29yZSBmdW5jdGlvbiBvZiAxLyhtZWFuKzJzZCkKCgpgYGB7cn0KIyBzY29yZXMgYnkgbmVhcmVzdCBhbWVuaXRpZXMKCm4xX3R0bV9zY29yZV8yIDwtIHN1bV9zY29yZV9meG5fMihuZWFyZXN0XzFfdHRtLCB3ZWlnaHQgPSBGQUxTRSwgbG9nX25vcm1hbGl6ZV9zY29yZSA9IFRSVUUsIG5vcm1hbGl6ZV9kZiA9IFRSVUUsIHggPSAxLCB5ID0gMTAwKQpuMV93dF90dG1fc2NvcmVfMiA8LSBzdW1fc2NvcmVfZnhuXzIobmVhcmVzdF8xX3R0bSwgd2VpZ2h0ID0gVFJVRSwgbG9nX25vcm1hbGl6ZV9zY29yZSA9IFRSVUUsIG5vcm1hbGl6ZV9kZiA9IFRSVUUsIHggPSAxLCB5ID0gMTAwKQoKbjJfdHRtX3Njb3JlXzIgPC0gc3VtX3Njb3JlX2Z4bl8yKG5lYXJlc3RfMl90dG0sIHdlaWdodCA9IEZBTFNFLCBsb2dfbm9ybWFsaXplX3Njb3JlID0gVFJVRSwgbm9ybWFsaXplX2RmID0gVFJVRSwgeCA9IDEsIHkgPSAxMDApCm4yX3d0X3R0bV9zY29yZV8yIDwtIHN1bV9zY29yZV9meG5fMihuZWFyZXN0XzJfdHRtLCB3ZWlnaHQgPSBUUlVFLCBsb2dfbm9ybWFsaXplX3Njb3JlID0gVFJVRSwgbm9ybWFsaXplX2RmID0gVFJVRSwgeCA9IDEsIHkgPSAxMDApCgpuM190dG1fc2NvcmVfMiA8LSBzdW1fc2NvcmVfZnhuXzIobmVhcmVzdF8zX3R0bSwgd2VpZ2h0ID0gRkFMU0UsIGxvZ19ub3JtYWxpemVfc2NvcmUgPSBUUlVFLCBub3JtYWxpemVfZGYgPSBUUlVFLCB4ID0gMSwgeSA9IDEwMCkKbjNfd3RfdHRtX3Njb3JlXzIgPC0gc3VtX3Njb3JlX2Z4bihuZWFyZXN0XzNfdHRtLCB3ZWlnaHQgPSBUUlVFLCBsb2dfbm9ybWFsaXplX3Njb3JlID0gVFJVRSwgbm9ybWFsaXplX2RmID0gVFJVRSwgeCA9IDEsIHkgPSAxMDApCgpgYGAKCgpgYGB7cn0KcGxvdF9kZW5zaXRpZXMobjFfdHRtX3Njb3JlXzIsIG4xX3d0X3R0bV9zY29yZV8yLCAnVW53ZWlnaHRlZCBTY29yZXMgd2l0aCAxLyhNZWFuICsgMipTZCknLCAnV2VpZ2h0ZWQgU2NvcmVzIHdpdGggMS8oTWVhbisyKlNkKScpCnBsb3RfZGVuc2l0aWVzKG4yX3R0bV9zY29yZV8yLCBuMl93dF90dG1fc2NvcmVfMiwgJ1Vud2VpZ2h0ZWQgU2NvcmVzIHdpdGggMS8oTWVhbiArIDIqU2QpJywgJ1dlaWdodGVkIFNjb3JlcyB3aXRoIDEvKE1lYW4rMipTZCknKQpwbG90X2RlbnNpdGllcyhuM190dG1fc2NvcmVfMiwgbjNfd3RfdHRtX3Njb3JlXzIsICdVbndlaWdodGVkIFNjb3JlcyB3aXRoIDEvKE1lYW4gKyAyKlNkKScsICdXZWlnaHRlZCBTY29yZXMgd2l0aCAxLyhNZWFuKzIqU2QpJykKCmBgYAoKIyMgRXhwb3J0aW5nIGFsbCBTY29yZSBTZXRzCgpgYGB7cn0KCiMjIEFkZCB3ZWlnaHQgY29sdW1uIGZvciBlYWNoIHNjb3JlIGZyYW1lCgp0dG1fc2NvcmVzJHdlaWdodCA8LSBhcy5mYWN0b3IoJ25vJykKdHRtX3d0ZF9zY29yZXMkd2VpZ2h0IDwtIGFzLmZhY3RvcigneWVzJykKCm4xX3R0bV9zY29yZSR3ZWlnaHQgPC0gYXMuZmFjdG9yKCdubycpCm4xX3d0X3R0bV9zY29yZSR3ZWlnaHQgPC0gYXMuZmFjdG9yKCd5ZXMnKQoKbjJfdHRtX3Njb3JlJHdlaWdodCA8LSBhcy5mYWN0b3IoJ25vJykKbjJfd3RfdHRtX3Njb3JlJHdlaWdodCA8LSBhcy5mYWN0b3IoJ3llcycpCgpuM190dG1fc2NvcmUkd2VpZ2h0IDwtIGFzLmZhY3Rvcignbm8nKQpuM193dF90dG1fc2NvcmUkd2VpZ2h0IDwtIGFzLmZhY3RvcigneWVzJykKCiMjIEFkZCBuZWFyZXN0X24gY29sdW1uIGZvciBlYWNoIHNjb3JlIGZyYW1lCgp0dG1fc2NvcmVzJG5lYXJlc3RfbiA8LSBhcy5mYWN0b3IoJ2FsbCcpCnR0bV93dGRfc2NvcmVzJG5lYXJlc3RfbiA8LSBhcy5mYWN0b3IoJ2FsbCcpCgpuMV90dG1fc2NvcmUkbmVhcmVzdF9uIDwtIGFzLmZhY3RvcignMScpCm4xX3d0X3R0bV9zY29yZSRuZWFyZXN0X24gPC0gYXMuZmFjdG9yKCcxJykKCm4yX3R0bV9zY29yZSRuZWFyZXN0X24gPC0gYXMuZmFjdG9yKCcyJykKbjJfd3RfdHRtX3Njb3JlJG5lYXJlc3RfbiA8LSBhcy5mYWN0b3IoJzInKQoKbjNfdHRtX3Njb3JlJG5lYXJlc3RfbiA8LSBhcy5mYWN0b3IoJzMnKQpuM193dF90dG1fc2NvcmUkbmVhcmVzdF9uIDwtIGFzLmZhY3RvcignMycpCgojIyBDb21iaW5lIGludG8gYSBsb25nIGRhdGFmcmFtZQphbGxfc2NvcmVzIDwtIGxpc3QodHRtX3Njb3JlcywgdHRtX3d0ZF9zY29yZXMsCiAgICAgICAgICAgICAgICAgICBuMV90dG1fc2NvcmUsIG4xX3d0X3R0bV9zY29yZSwKICAgICAgICAgICAgICAgICAgIG4yX3R0bV9zY29yZSwgbjJfd3RfdHRtX3Njb3JlLAogICAgICAgICAgICAgICAgICAgbjNfdHRtX3Njb3JlLCBuM193dF90dG1fc2NvcmUpCgpsb25nX3Njb3JlcyA8LSBkYXRhLnRhYmxlOjpyYmluZGxpc3QoYWxsX3Njb3JlcykgJT4lIGFycmFuZ2UoZnJvbUlkKQoKIyMgUmUtT3JkZXIgY29sdW1ucwpsb25nX3Njb3JlcyA8LSBsb25nX3Njb3Jlc1ssIGMoMSwgMiwgNCwgNSwgMyldCgojIyBFeHBvcnQKd3JpdGUuY3N2KGxvbmdfc2NvcmVzLCAnLi4vLi4vZGF0YS9zY29yZV9zZXRzL2xvbmdfc2NvcmVzLmNzdicsIHJvdy5uYW1lcyA9IEZBTFNFKQpgYGAKCgoKCgoKCiMgT2xkIE5vdGVzIH4gSWdub3JlIG9yIHJldXNlIGxhdGVyCgp8IE5hbWUgfCBGdW5jdGlvbiB8IE5vdGVzIHwgQXNzdW1wdGlvbnMgfAp8LS0tfC0tLXwtLS18LS0tfAp8VW53ZWlnaHRlZCBOYWl2ZSB8IG51bWJlciBvZiBhY2Nlc3NpYmxlIHBvaW50cyAvIChtZWFuIHRyYW5zaXQgdGltZSAqIG1lYW4gc3RhbmRhcmQgZGV2aWF0aW9uIGluIHRyYW5zaXQgdGltZSkgIHwgTWVhbiB0cmFuc2l0IHRpbWUgdG8gYWxsIGFjY2Vzc2libGUgZGVzdGluYXRpb25zICB8IEFzc3VtZXMgdGhhdCBhY2Nlc3NpYmlsaXR5IGlzIGRlZmluZWQgYnkgYWNjZXNzIHRvIGFsbCBhbWVuaXRpZXMgfAp8V2VpZ2h0ZWQgTmFpdmUgfCBwb3B1bGFyaXR5IHdlaWdodGVkIGFjY2Vzc2libGUgcG9pbnRzIC8gKG1lYW4gdHJhbnNpdCB0aW1lICogbWVhbiBzdGFuZGFyZCBkZXZpYXRpb24gaW4gdHJhbnNpdCB0aW1lKSAgfCBNZWFuIHRyYW5zaXQgdGltZSB0byBhbGwgYWNjZXNzaWJsZSBkZXN0aW5hdGlvbnMgIHwgQXNzdW1lcyB0aGF0IGFjY2Vzc2liaWxpdHkgaXMgZGVmaW5lZCBieSBhY2Nlc3MgdG8gYWxsIGFtZW5pdGllcyBhbmQgdGhhdCBhbWVuaXR5IHBvcHVsYXJpdHkgZGVmaW5lcyBzaWduaWZpY2FuY2Ugb2YgYW4gYWNjZXNzaWJsZSBhbWVuaXR5IHwKfFVud2VpZ2h0ZWQgU3VtIHwgMSAvIChuZWFyZXN0IGFtZW5pdHkgdHJhbnNpdCB0aW1lICsgc3RhbmRhcmQgZGV2aWF0aW9uIGluIG5lYXJlc3QgdHJhbnNpdCB0aW1lKSAgfCBPbmx5IGNvbnNpZGVycyB0aGUgbmVhcmVzdCAxIHRvIDMgYW1lbml0aWVzIG9mIGEgY2VydGFpbiBjYXRlZ29yeS4gU3VtIGlzIHVzZWQgdG8gcHJldmVudCBza2V3aW5nIG9mIGRhdGEgKGRpZmZlcmVuY2UoMS8oMC4wMVwqMC4wMSkgYW5kIDEvKDZcKjYpKSA+Pj4gZGlmZmVyZW5jZSgxLygwLjAxKzAuMDEpIGFuZCAxLyg2KzYpKSkgfCBBc3N1bWVzIGFjY2Vzc2liaWxpdHkgb25seSBkZWZpbmVkIGJ5IGFjY2VzcyB0byB0aGUgbmVhcmVzdCBhbWVuaXR5IHR5cGUgIHwKfCBKb3NlcGggVW53ZWlnaHRlZCBTdW0gfCBzdW0oMSAvIChub3JtYWxpemVkX3RyYW5zaXRfdGltZV9pKm5vcm1hbGl6ZWRfc2RfdGltZV9pKSkgfCBTdW1zIHRoZSB0cmFuc2l0IHRpbWVzIGFzIG9wcG9zZWQgdG8gdGFraW5nIHRoZSBtZWFuLCB0aGVuIG5vcm1hbGl6ZXMgdGhlIHNjb3Jlcy4gfAoKCgoKCgoKCgoKCgoKCgoK